package com.hyw.trace.configuration;

import java.util.List;

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hyw.trace.configuration.meta.IEventStorage;
import com.hyw.trace.configuration.meta.TraceCollector;
import com.hyw.trace.configuration.meta.TraceEvent;
import com.hyw.trace.configuration.meta.mq.binder.ZTraceSink;
import com.hyw.trace.configuration.meta.redis.queue.ICacheQueue;
import com.hyw.trace.configuration.meta.redis.queue.RedisQueueBuilder;

@Configuration
public class ZTraceServerAutoConfiguration {

  private static final Logger log = LoggerFactory.getLogger(ZTraceServerAutoConfiguration.class);

  /**
   * @return 提供一个默认的收集器实现器,方便新手使用, 会将采集到的数据,打印到日志上
   * @author hongyu
   */
  @Bean
  @ConditionalOnMissingBean(value = TraceCollector.class)
  public TraceCollector<TraceEvent, TraceEvent> loggingCollector() {
    log.warn("Cannot found any TraceCollector instance, the default TraceCollector for printing will be actived.");
    return new LoggerDisplayCollector();
  }

  private static class LoggerDisplayCollector extends TraceCollector<TraceEvent, TraceEvent> {
    private static final Logger log = LoggerFactory.getLogger(LoggerDisplayCollector.class);

    public LoggerDisplayCollector() {
      super(null);
    }

    public LoggerDisplayCollector(IEventStorage<TraceEvent> storage) {
      super(storage);
    }

    @Override
    public void collect(TraceEvent event) {
      log.info("Collecting:{}", event);
    }

    @Override
    public boolean isSupport(TraceEvent event) {
      return true;
    }

  }

  /**
   * 收集器端的Redis配置
   * 
   * @author hongyu
   */
  @Configuration(proxyBeanMethods = false)
  @ConditionalOnProperty(name = "ztrace.server.meta.receive.type", havingValue = "REDIS")
  public static class RedisBinderConfiguration {
    private static final Logger log = LoggerFactory.getLogger(RedisBinderConfiguration.class);

    private final ObjectMapper obj = new ObjectMapper();

    @SuppressWarnings("rawtypes")
    protected final List<TraceCollector> collectors;
    protected final ICacheQueue<String> simpleQueue;

    @SuppressWarnings("unchecked")
    public RedisBinderConfiguration(@SuppressWarnings("rawtypes") @Autowired List<TraceCollector> collectors,
        RedisTemplate<String, String> redisTemplate, @Value("${ztrace.server.meta.redis.topic:ZTrace") String topic) {
      this.collectors = collectors;
      log.info("Applicable approach 'REDIS' is actived for ztrace server.And listening on topic: {}", topic);
      simpleQueue = RedisQueueBuilder.create(topic, redisTemplate).setConsumer(s -> {
        try {
          TraceEvent event = obj.readValue(s, TraceEvent.class);
          collectors.forEach(c -> {
            try {
              if (c.isSupport(event)) {
                c.collect(event);
              }
            } catch (Exception ex) {
              log.error("", ex);
            }
          });
        } catch (JsonProcessingException e) {
          ReflectionUtils.rethrowRuntimeException(e);
        }
      }).build();
    }

  }

  /**
   * 收集器端的HTTP配置
   * 
   * @author hongyu
   */
  @Configuration(proxyBeanMethods = false)
  @ConditionalOnProperty(name = "ztrace.server.meta.receive.type", havingValue = "HTTP")
  public static class HTTPBinderConfiguration {
    private static final Logger log = LoggerFactory.getLogger(HTTPBinderConfiguration.class);

    /**
     * 一个默认的接收点
     */
    private static final String End_Point = "/ztrace/collect";

    @SuppressWarnings("rawtypes")
    protected List<TraceCollector> collectors;

    public HTTPBinderConfiguration(@SuppressWarnings("rawtypes") @Autowired List<TraceCollector> collectors) {
      this.collectors = collectors;
      log.info("Applicable approach 'HTTP' is actived for ztrace server.And listening on EndPoint: /ztrace/collect");
    }

    @Validated
    @RestController
    public class ZTraceController {
      @SuppressWarnings("unchecked")
      @PostMapping(End_Point)
      public String collect(@RequestBody @Valid TraceEvent event) {
        collectors.forEach(c -> {
          try {
            if (c.isSupport(event)) {
              c.collect(event);
            }
          } catch (Exception ex) {
            log.error("", ex);
          }
        });
        return "OK";
      }
    }
  }

  /**
   * 收集器端的MQ配置
   * 
   * @author hongyu
   */
  @Configuration(proxyBeanMethods = false)
  @EnableBinding(value = { ZTraceSink.class })
  @ConditionalOnProperty(name = "ztrace.server.meta.receive.type", havingValue = "MQ", matchIfMissing = true)
  public static class MQBinderConfiguration {
    private static final Logger log = LoggerFactory.getLogger(MQBinderConfiguration.class);

    @SuppressWarnings("rawtypes")
    protected List<TraceCollector> collectors;

    public MQBinderConfiguration(@SuppressWarnings("rawtypes") @Autowired List<TraceCollector> collectors) {
      this.collectors = collectors;
      log.info("Applicable approach 'MQ' is actived for ztrace server.And listening on Topic:{}", ZTraceSink.INPUT);
    }

    @SuppressWarnings("unchecked")
    @StreamListener(ZTraceSink.INPUT)
    public void onEvent(TraceEvent event) {
      collectors.forEach(c -> {
        try {
          if (c.isSupport(event)) {
            c.collect(event);
          }
        } catch (Exception ex) {
          log.error("", ex);
        }
      });
    }
  }

}
